feat(devx): hold this repo's own shell to a bash 3.2 floor - #8016
Merged
Conversation
macOS ships bash 3.2.57 and no bash 4+; CI runs bash 5. So a `mapfile`, a `declare -A` or an unguarded `$EPOCHSECONDS` in a hand-run script is green in CI, green locally on Linux, and dies only on the host the floor exists for. This repository held its shell to nothing: measured on `origin/main` 28cfff4, 0 tracked files named bash32/bash-32 against a control of 47 existing `scripts/check-*.mjs` gates. Ports `scripts/check-bash32-floor.mjs` from objectstack at 6136293 with THIS repository's scan roots. `e2e/**` replaces upstream's `.githooks/**` and is the substantive change: two of the four shell files this repository wrote itself live in `e2e/live/ci/`, so upstream's roots would have walked past half the population while printing a green line. First full-population run on this tree: 12 shell files, 19 constructs, 0 findings — a ratchet, not a repair. The card that filed it had grepped 10 of the 19 constructs by hand; the gate makes the other 9 a measurement. Every claim in the ported header that was a measurement about the SIBLING tree is re-measured here or labelled as upstream's. Two self-test legs differ from upstream and both grew rather than shrank: the clean-tree fixture now asserts both census halves by number instead of matching the word "shebang" (which the green line prints unconditionally), and upstream's `byShebang > 0` real-tree leg is replaced by a census-partition leg plus an every-member-was-read leg, because all 12 shell files here carry a `.sh` name. Deliberately NOT registered in `scripts/upstream-port-pin.json`: that ledger carries one global `upstream.ref` (bf10deb) which `--resync` rewrites for every entry, so registering would mean either shipping bf10deb's older, weaker construct table or re-syncing three unrelated ported files. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
This was referenced Sep 6, 2026
Contributor
|
Standing-down note — Generated by Claude Code |
baozhoutao
marked this pull request as ready for review
September 6, 2026 10:06
baozhoutao
pushed a commit
that referenced
this pull request
Sep 6, 2026
Batch 19 (PR #8017, packages/collaboration/README.md) and objectui#7692 (PR #8016) landed while this batch was measuring. The two UNGATED_DOCS removals are different lines of the same object literal, so the merge is clean; every gate, test, probe and reading is re-run on the merged head. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
This was referenced Sep 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7692
macOS ships bash 3.2.57 and no bash 4+, for licensing reasons. CI runs bash 5, where every bash-4-only construct works. So a
mapfile, adeclare -Aor an unguarded$EPOCHSECONDSin a hand-run script is invisible to a normal green run: the defect and its repair both read as green, and the only reader who ever sees the failure is a contributor on a Mac at the moment they most need the script to work.This repository held its own shell to nothing. The sibling repo has gated every tracked shell file at bash 3.2 for some time; here, four shell files this repository wrote itself were held to no floor at all — and two of them (
e2e/live/ci/start-backend.sh,e2e/live/ci/stop-backend.sh) are not even under the sibling gate's scan roots.What landed
scripts/check-bash32-floor.mjsscripts/check-bash32-floor.mjsat6136293, with this repository's scan rootsscripts/__tests__/bash32-floor-wiring.test.tsentry-guard-wiring.test.tspackage.jsoncheck:bash32-floor.github/workflows/lint.ymllintjob, beside the other pre-install repo gatesNo new workflow. No timeout change. No
.shfile edited. No changeset owed (check-changeset-presence: "0 of them published source of a package the release covers … no changeset is owed").The one substantive change:
e2e/**Upstream declares
scripts/**,.claude/hooks/**,.githooks/**. This port declaresscripts/**,.claude/hooks/**,e2e/**.This repository has no
.githooks/at all, and two of the four shell files it wrote itself live undere2e/. Copying upstream's roots verbatim would have walked past half the population this card is about while printing a confident green line — which is the exact failure the gate exists to refuse. The root is pinned in both directions inbash32-floor-wiring.test.ts: that it is declared, and that the walk actually honours it.The measurement the card asked for
The card grepped 10 of the 19 constructs by hand and was explicit that the result must not be quoted as a clean bill. Here is the full-population verdict, which is the gate's own output on this tree at
17c1b86:19 of 19, all 12 files, 0 findings. So this is a ratchet, not a repair — and the other 9 constructs are now a measurement rather than an assumption.
Evidence that the instrument can fail
A gate whose pattern matches nothing reports a clean tree forever, so each leg below was driven on disk (mutation proven by anchored counts plus
git hash-objectagainst the HEAD blob, restored under atrapwith absolute paths, restore proven by an emptygit diff HEAD).Reach of the
e2e/**root — the whole point of the card. Adeclare -A backend_envplanted ine2e/live/ci/start-backend.sh:The self-test really fails. Narrowing the roots back to upstream's reds the wiring pin (3 of 9 cases); gutting the
assoc-arraypattern reds--self-test(4 of 155 cases). Both restored to the HEAD bytes.What this port changed about upstream's text, and why
Every claim in the ported header that was a measurement about the sibling tree is either re-measured here or labelled as upstream's. The two that changed behaviour:
byShebang > 0leg is gone. Upstream measures 2 shebang-only scripts and calls the discovery half load-bearing. Measured here: all 12 shell files carry a.shname, so the shebang census is 0 and upstream's leg would have been red on day one. ⛔ The wrong repair would be to drop shebang discovery — it is the half that catches the first extension-less script anyone adds. Instead its mechanism moved to where it can actually be exercised (the end-to-end fixture now plants a shebang-only script under a walked root), and the real-tree battery got two legs that are true here: the census partitions the population, and every member was really read./shebang/on the green line — a word that line prints unconditionally, so the leg passes on a discovery path that found nothing.Both self-test battery floors grew (5→6, 2→3); neither was lowered. Case count 153 → 155.
Why this is not registered in
scripts/upstream-port-pin.jsonThe ledger would be valuable here — this file's whole subject is that a denylist rots, and upstream actively sweeps the table. It is still not registered, for a structural reason rather than a judgement:
The pin carries one global
upstream.ref(bf10debtoday), not one per file, and--resyncrewrites it for every entry (pin.upstream.ref = ref;). This port was taken at6136293. Measured over the API, the upstream file exists at both refs — 55,415 bytes atbf10debagainst 62,481 at6136293— and the difference is the-vunary widening plus the 4.0 operator sweep. So registering would mean either deliberately shipping the older, weaker construct table, or bumping the global ref and re-syncing three unrelated ported files. ⛔ The third option — register6136293's digest under a global ref that readsbf10deb— is the one that must not be taken: the digest would verify while the provenance line was false.This limitation is already owned by objectui#7953, measured from the other direction (two ported hooks that do not exist upstream at
bf10debat all). Not re-filed. The consequence is written into the gate's header so it is inherited rather than rediscovered: this file has no drift gate.Falsified dispatch assumptions
--self-test, 153 cases). This repository's idiom for exactly that shape (check-entry-guard,check-upstream-port-parity) is inline battery + a*-wiring.test.tspin, so that is what shipped.ci-cd-pipeline-doc.test.tsdoes not red, and no doc row is owed. Its job-table and first-party-command pins readci.ymlonly (const ciWorkflow = fs.readFileSync(path.join(workflowDir, 'ci.yml'), 'utf8'));lint.ymlis covered only by the workflow-inventory pin, which wants a heading and already has one. Run targeted: 40 passed.timeout-minutes: 25is derived from this job's own distribution (max 7m14s). Both invocations of this gate together run in well under two seconds.ci-cd-pipeline.md's Lint section names 1 of the 4scripts/check-*.mjsgates that job runs —check-entry-guard,check-upstream-port-parityandcheck-cross-repo-closer-outcomewere already absent before this PR, and this step would be a fourth. Deliberately not patched here: updating the prose alone repeats the drift, and a partially-refreshed list reads as curated. Filed unassigned as objectui#8015.Gates run (all at
17c1b86, exit codes captured by redirect-then-capture)pnpm check:bash32-floornode scripts/check-bash32-floor.mjs --self-testpnpm exec vitest run scripts/__tests__/pnpm type-check:scriptspnpm lint:root(whole, no narrowing)pnpm check:entry-guardpnpm check:upstream-port-paritypnpm check:control-bytesnode scripts/check-pre-install-import-graph.mjsnode scripts/check-changeset-presence.mjsnode scripts/check-governed-queue-guard.mjs --test …Plus a
grep -naPcontrol-byte self-scan of all four changed paths: clean.Live E2E (informational)is red on every branch today for an upstream reason and is not this change's.Generated by Claude Code